<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta name="keywords" content="fescar、seata、分布式事务" />
	<meta name="description" content="seata-analysis-java-client" />
	<!-- 网页标签标题 -->
	<title>分布式事务之Seata-Client原理及流程详解</title>
  <link rel="shortcut icon" href="/img/seata_logo_small.jpeg"/>
	<link rel="stylesheet" href="/build/blogDetail.css" />
</head>
<body>
	<div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/seata_logo.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/system/menu_gray.png"/><ul><li class="menu-item menu-item-normal"><a href="/zh-cn/index.html" target="_self">首页</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">文档</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/docs/developers/developers_dev.html" target="_self">开发者</a></li><li class="menu-item menu-item-normal menu-item-normal-active"><a href="/zh-cn/blog/index.html" target="_self">博客</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/community/index.html" target="_self">社区</a></li><li class="menu-item menu-item-normal"><a href="/zh-cn/blog/download.html" target="_self">下载</a></li></ul></div></div></header><section class="blog-content markdown-body"><h2>前言</h2>
<p>在分布式系统中，分布式事务是一个必须要解决的问题，目前使用较多的是最终一致性方案。自年初阿里开源了Fescar（四月初更名为Seata）后，该项目受到了极大的关注度，目前已接近8000Star。<a href="https://github.com/seata/seata">Seata</a>以高性能和零侵入的方式为目标解决微服务领域的分布式事务难题，目前正处于快速迭代中，近期小目标是生产可用的Mysql版本。关于Seata的总体介绍，可以查看<a href="https://github.com/seata/seata/wiki/%E6%A6%82%E8%A7%88">官方WIKI</a>获得更多更全面的内容介绍。</p>
<p>本文主要基于spring cloud+spring jpa+spring cloud alibaba fescar+mysql+seata的结构，搭建一个分布式系统的demo，通过seata的debug日志和源代码，从client端（RM、TM）的角度分析说明其工作流程及原理。</p>
<p>文中代码基于fescar-0.4.1，由于项目刚更名为seata不久，例如一些包名、类名、jar包名称还都是fescar的命名，故下文中仍使用fescar进行表述。</p>
<p>示例项目：<a href="https://github.com/fescar-group/fescar-samples/tree/master/springcloud-jpa-seata">https://github.com/fescar-group/fescar-samples/tree/master/springcloud-jpa-seata</a></p>
<h2>相关概念</h2>
<ul>
<li>XID：全局事务的唯一标识，由ip:port:sequence组成</li>
<li>Transaction Coordinator (TC)：事务协调器，维护全局事务的运行状态，负责协调并驱动全局事务的提交或回滚</li>
<li>Transaction Manager (TM )：控制全局事务的边界，负责开启一个全局事务，并最终发起全局提交或全局回滚的决议</li>
<li>Resource Manager (RM)：控制分支事务，负责分支注册、状态汇报，并接收事务协调器的指令，驱动分支（本地）事务的提交和回滚</li>
</ul>
<h2>分布式框架支持</h2>
<p>Fescar使用XID表示一个分布式事务，XID需要在一次分布式事务请求所涉的系统中进行传递，从而向feacar-server发送分支事务的处理情况，以及接收feacar-server的commit、rollback指令。
Fescar官方已支持全版本的dubbo协议，而对于spring cloud（spring-boot）的分布式项目社区也提供了相应的实现</p>
<pre><code class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-alibaba-fescar<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.1.0.BUILD-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<p>该组件实现了基于RestTemplate、Feign通信时的XID传递功能。</p>
<h2>业务逻辑</h2>
<p>业务逻辑是经典的下订单、扣余额、减库存流程。
根据模块划分为三个独立的服务，且分别连接对应的数据库</p>
<ul>
<li>订单：order-server</li>
<li>账户：account-server</li>
<li>库存：storage-server</li>
</ul>
<p>另外还有发起分布式事务的业务系统</p>
<ul>
<li>业务：business-server</li>
</ul>
<p>项目结构如下图
<img src="/img/blog/20190410114411366.png" alt="在这里插入图片描述"></p>
<p><strong>正常业务</strong></p>
<ol>
<li>business发起购买请求</li>
<li>storage扣减库存</li>
<li>order创建订单</li>
<li>account扣减余额</li>
</ol>
<p><strong>异常业务</strong></p>
<ol>
<li>business发起购买请求</li>
<li>storage扣减库存</li>
<li>order创建订单</li>
<li>account<code>扣减余额异常</code></li>
</ol>
<p>正常流程下2、3、4步的数据正常更新全局commit，异常流程下的数据则由于第4步的异常报错全局回滚。</p>
<h2>配置文件</h2>
<p>fescar的配置入口文件是<a href="https://github.com/seata/seata/blob/develop/config/src/main/resources/registry.conf">registry.conf</a>,查看代码<a href="https://github.com/seata/seata/blob/develop/config/src/main/java/com/alibaba/fescar/config/ConfigurationFactory.java">ConfigurationFactory</a>得知目前还不能指定该配置文件，所以配置文件名称只能为registry.conf</p>
<pre><code class="language-java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String REGISTRY_CONF = <span class="hljs-string">"registry.conf"</span>;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Configuration FILE_INSTANCE = <span class="hljs-keyword">new</span> FileConfiguration(REGISTRY_CONF);
</code></pre>
<p>在<code>registry</code>中可以指定具体配置的形式，默认使用file类型，在file.conf中有3部分配置内容</p>
<ol>
<li>transport
transport部分的配置对应<a href="https://github.com/seata/seata/blob/develop/core/src/main/java/com/alibaba/fescar/core/rpc/netty/NettyServerConfig.java">NettyServerConfig</a>类，用于定义Netty相关的参数，TM、RM与fescar-server之间使用Netty进行通信</li>
<li>service</li>
</ol>
<pre><code class="language-js">	 service {
	  #vgroup-&gt;rgroup
	  vgroup_mapping.my_test_tx_group = "default"
	  #配置Client连接TC的地址
	  default.grouplist = "127.0.0.1:8091"
	  #degrade current not support
	  enableDegrade = false
	  #disable
	  是否启用seata的分布式事务
	  disableGlobalTransaction = false
	}
</code></pre>
<ol start="3">
<li>client</li>
</ol>
<pre><code class="language-js">	client {
	  #RM接收TC的commit通知后缓冲上限
	  async.commit.buffer.limit = 10000
	  lock {
	    retry.internal = 10
	    retry.times = 30
	  }
	}
</code></pre>
<h2>数据源Proxy</h2>
<p>除了前面的配置文件，fescar在AT模式下稍微有点代码量的地方就是对数据源的代理指定，且目前只能基于<code>DruidDataSource</code>的代理。
注：在最新发布的0.4.2版本中已支持任意数据源类型</p>
<pre><code class="language-java"><span class="hljs-meta">@Bean</span>
<span class="hljs-meta">@ConfigurationProperties</span>(prefix = <span class="hljs-string">"spring.datasource"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> DruidDataSource <span class="hljs-title">druidDataSource</span><span class="hljs-params">()</span> </span>{
    DruidDataSource druidDataSource = <span class="hljs-keyword">new</span> DruidDataSource();
    <span class="hljs-keyword">return</span> druidDataSource;
}

<span class="hljs-meta">@Primary</span>
<span class="hljs-meta">@Bean</span>(<span class="hljs-string">"dataSource"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> DataSourceProxy <span class="hljs-title">dataSource</span><span class="hljs-params">(DruidDataSource druidDataSource)</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DataSourceProxy(druidDataSource);
}
</code></pre>
<p>使用<code>DataSourceProxy</code>的目的是为了引入<code>ConnectionProxy</code>,fescar无侵入的一方面就体现在<code>ConnectionProxy</code>的实现上，即分支事务加入全局事务的切入点是在本地事务的<code>commit</code>阶段，这样设计可以保证业务数据与<code>undo_log</code>是在一个本地事务中。</p>
<p><code>undo_log</code>是需要在业务库上创建的一个表，fescar依赖该表记录每笔分支事务的状态及二阶段<code>rollback</code>的回放数据。不用担心该表的数据量过大形成单点问题，在全局事务<code>commit</code>的场景下事务对应的<code>undo_log</code>会异步删除。</p>
<pre><code class="language-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-string">`undo_log`</span> (
  <span class="hljs-string">`id`</span> <span class="hljs-built_in">bigint</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> AUTO_INCREMENT,
  <span class="hljs-string">`branch_id`</span> <span class="hljs-built_in">bigint</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`xid`</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`rollback_info`</span> longblob <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`log_status`</span> <span class="hljs-built_in">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`log_created`</span> datetime <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`log_modified`</span> datetime <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`ext`</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-literal">NULL</span>,
  PRIMARY <span class="hljs-keyword">KEY</span> (<span class="hljs-string">`id`</span>),
  <span class="hljs-keyword">UNIQUE</span> <span class="hljs-keyword">KEY</span> <span class="hljs-string">`ux_undo_log`</span> (<span class="hljs-string">`xid`</span>,<span class="hljs-string">`branch_id`</span>)
) <span class="hljs-keyword">ENGINE</span>=<span class="hljs-keyword">InnoDB</span> AUTO_INCREMENT=<span class="hljs-number">1</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CHARSET</span>=utf8;
</code></pre>
<h2>启动Server</h2>
<p>前往<a href="https://github.com/seata/seata/releases">https://github.com/seata/seata/releases</a> 下载与Client版本对应的fescar-server,避免由于版本的不同导致的协议不一致问题
进入解压之后的 bin 目录，执行</p>
<pre><code class="language-shell">./fescar-server.sh 8091 ../data
</code></pre>
<p>启动成功输出</p>
<pre><code class="language-shell">2019-04-09 20:27:24.637 INFO [main]c.a.fescar.core.rpc.netty.AbstractRpcRemotingServer.start:152 -Server started ... 
</code></pre>
<h2>启动Client</h2>
<p>fescar的加载入口类位于<a href="https://github.com/spring-cloud-incubator/spring-cloud-alibaba/blob/finchley/spring-cloud-alibaba-fescar/src/main/java/org/springframework/cloud/alibaba/fescar/GlobalTransactionAutoConfiguration.java">GlobalTransactionAutoConfiguration</a>，对基于spring boot的项目能够自动加载，当然也可以通过其他方式示例化<code>GlobalTransactionScanner</code></p>
<pre><code class="language-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-meta">@EnableConfigurationProperties</span>({FescarProperties.class})
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalTransactionAutoConfiguration</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ApplicationContext applicationContext;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> FescarProperties fescarProperties;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">GlobalTransactionAutoConfiguration</span><span class="hljs-params">(ApplicationContext applicationContext, FescarProperties fescarProperties)</span> </span>{
        <span class="hljs-keyword">this</span>.applicationContext = applicationContext;
        <span class="hljs-keyword">this</span>.fescarProperties = fescarProperties;
    }

	<span class="hljs-comment">/**
	* 示例化GlobalTransactionScanner
	* scanner为client初始化的发起类
	*/</span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> GlobalTransactionScanner <span class="hljs-title">globalTransactionScanner</span><span class="hljs-params">()</span> </span>{
        String applicationName = <span class="hljs-keyword">this</span>.applicationContext.getEnvironment().getProperty(<span class="hljs-string">"spring.application.name"</span>);
        String txServiceGroup = <span class="hljs-keyword">this</span>.fescarProperties.getTxServiceGroup();
        <span class="hljs-keyword">if</span> (StringUtils.isEmpty(txServiceGroup)) {
            txServiceGroup = applicationName + <span class="hljs-string">"-fescar-service-group"</span>;
            <span class="hljs-keyword">this</span>.fescarProperties.setTxServiceGroup(txServiceGroup);
        }
		
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> GlobalTransactionScanner(applicationName, txServiceGroup);
    }
}
</code></pre>
<p>可以看到支持一个配置项FescarProperties，用于配置事务分组名称</p>
<pre><code class="language-json">spring.cloud.alibaba.fescar.tx-service-group=my_test_tx_group
</code></pre>
<p>如果不指定服务组，则默认使用spring.application.name+ -fescar-service-group生成名称，所以不指定spring.application.name启动会报错</p>
<pre><code class="language-java"><span class="hljs-meta">@ConfigurationProperties</span>(<span class="hljs-string">"spring.cloud.alibaba.fescar"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FescarProperties</span> </span>{
    <span class="hljs-keyword">private</span> String txServiceGroup;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FescarProperties</span><span class="hljs-params">()</span> </span>{
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getTxServiceGroup</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.txServiceGroup;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setTxServiceGroup</span><span class="hljs-params">(String txServiceGroup)</span> </span>{
        <span class="hljs-keyword">this</span>.txServiceGroup = txServiceGroup;
    }
}
</code></pre>
<p>获取applicationId和txServiceGroup后，创建<a href="https://github.com/seata/seata/blob/develop/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionScanner.java">GlobalTransactionScanner</a>对象，主要看类中initClient方法</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initClient</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span> (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(
            <span class="hljs-string">"applicationId: "</span> + applicationId + <span class="hljs-string">", txServiceGroup: "</span> + txServiceGroup);
    }
    <span class="hljs-comment">//init TM</span>
    TMClient.init(applicationId, txServiceGroup);

    <span class="hljs-comment">//init RM</span>
    RMClient.init(applicationId, txServiceGroup);
  
}
</code></pre>
<p>方法中可以看到初始化了<code>TMClient</code>和<code>RMClient</code>，对于一个服务既可以是TM角色也可以是RM角色，至于什么时候是TM或者RM则要看在一次全局事务中<code>@GlobalTransactional</code>注解标注在哪。
Client创建的结果是与TC的一个Netty连接，所以在启动日志中可以看到两个Netty Channel，其中标明了transactionRole分别为<code>TMROLE</code>和<code>RMROLE</code></p>
<pre><code class="language-java"><span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.417</span>  INFO <span class="hljs-number">93715</span> --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory   : NettyPool create channel to {<span class="hljs-string">"address"</span>:<span class="hljs-string">"127.0.0.1:8091"</span>,<span class="hljs-string">"message"</span>:{<span class="hljs-string">"applicationId"</span>:<span class="hljs-string">"business-service"</span>,<span class="hljs-string">"byteBuffer"</span>:{<span class="hljs-string">"char"</span>:<span class="hljs-string">"\u0000"</span>,<span class="hljs-string">"direct"</span>:<span class="hljs-keyword">false</span>,<span class="hljs-string">"double"</span>:<span class="hljs-number">0.0</span>,<span class="hljs-string">"float"</span>:<span class="hljs-number">0.0</span>,<span class="hljs-string">"int"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"long"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"readOnly"</span>:<span class="hljs-keyword">false</span>,<span class="hljs-string">"short"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"transactionServiceGroup"</span>:<span class="hljs-string">"my_test_tx_group"</span>,<span class="hljs-string">"typeCode"</span>:<span class="hljs-number">101</span>,<span class="hljs-string">"version"</span>:<span class="hljs-string">"0.4.1"</span>},<span class="hljs-string">"transactionRole"</span>:<span class="hljs-string">"TMROLE"</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.505</span>  INFO <span class="hljs-number">93715</span> --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory   : NettyPool create channel to {<span class="hljs-string">"address"</span>:<span class="hljs-string">"127.0.0.1:8091"</span>,<span class="hljs-string">"message"</span>:{<span class="hljs-string">"applicationId"</span>:<span class="hljs-string">"business-service"</span>,<span class="hljs-string">"byteBuffer"</span>:{<span class="hljs-string">"char"</span>:<span class="hljs-string">"\u0000"</span>,<span class="hljs-string">"direct"</span>:<span class="hljs-keyword">false</span>,<span class="hljs-string">"double"</span>:<span class="hljs-number">0.0</span>,<span class="hljs-string">"float"</span>:<span class="hljs-number">0.0</span>,<span class="hljs-string">"int"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"long"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"readOnly"</span>:<span class="hljs-keyword">false</span>,<span class="hljs-string">"short"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"transactionServiceGroup"</span>:<span class="hljs-string">"my_test_tx_group"</span>,<span class="hljs-string">"typeCode"</span>:<span class="hljs-number">103</span>,<span class="hljs-string">"version"</span>:<span class="hljs-string">"0.4.1"</span>},<span class="hljs-string">"transactionRole"</span>:<span class="hljs-string">"RMROLE"</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.629</span> DEBUG <span class="hljs-number">93715</span> --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:RegisterTMRequest{applicationId=<span class="hljs-string">'business-service'</span>, transactionServiceGroup=<span class="hljs-string">'my_test_tx_group'</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.629</span> DEBUG <span class="hljs-number">93715</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:RegisterRMRequest{resourceIds=<span class="hljs-string">'null'</span>, applicationId=<span class="hljs-string">'business-service'</span>, transactionServiceGroup=<span class="hljs-string">'my_test_tx_group'</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.699</span> DEBUG <span class="hljs-number">93715</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:version=<span class="hljs-number">0.4</span>.1,extraData=<span class="hljs-keyword">null</span>,identified=<span class="hljs-keyword">true</span>,resultCode=<span class="hljs-keyword">null</span>,msg=<span class="hljs-keyword">null</span>,messageId:<span class="hljs-number">1</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.699</span> DEBUG <span class="hljs-number">93715</span> --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:version=<span class="hljs-number">0.4</span>.1,extraData=<span class="hljs-keyword">null</span>,identified=<span class="hljs-keyword">true</span>,resultCode=<span class="hljs-keyword">null</span>,msg=<span class="hljs-keyword">null</span>,messageId:<span class="hljs-number">2</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.701</span> DEBUG <span class="hljs-number">93715</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : com.alibaba.fescar.core.rpc.netty.RmRpcClient@<span class="hljs-number">3</span>b06d101 msgId:<span class="hljs-number">1</span>, future :com.alibaba.fescar.core.protocol.MessageFuture@<span class="hljs-number">28</span>bb1abd, body:version=<span class="hljs-number">0.4</span>.1,extraData=<span class="hljs-keyword">null</span>,identified=<span class="hljs-keyword">true</span>,resultCode=<span class="hljs-keyword">null</span>,msg=<span class="hljs-keyword">null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.701</span> DEBUG <span class="hljs-number">93715</span> --- [lector_TMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : com.alibaba.fescar.core.rpc.netty.TmRpcClient@<span class="hljs-number">65f</span>c3fb7 msgId:<span class="hljs-number">2</span>, future :com.alibaba.fescar.core.protocol.MessageFuture@<span class="hljs-number">9</span>a1e3df, body:version=<span class="hljs-number">0.4</span>.1,extraData=<span class="hljs-keyword">null</span>,identified=<span class="hljs-keyword">true</span>,resultCode=<span class="hljs-keyword">null</span>,msg=<span class="hljs-keyword">null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.710</span>  INFO <span class="hljs-number">93715</span> --- [imeoutChecker_1] c.a.fescar.core.rpc.netty.RmRpcClient    : register RM success. server version:<span class="hljs-number">0.4</span>.1,channel:[id: <span class="hljs-number">0xe6468995</span>, L:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">57397</span> - R:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">8091</span>]
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.710</span>  INFO <span class="hljs-number">93715</span> --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory   : register success, cost <span class="hljs-number">114</span> ms, version:<span class="hljs-number">0.4</span>.1,role:TMROLE,channel:[id: <span class="hljs-number">0xd22fe0c5</span>, L:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">57398</span> - R:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">8091</span>]
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">42</span>:<span class="hljs-number">57.711</span>  INFO <span class="hljs-number">93715</span> --- [imeoutChecker_1] c.a.f.c.rpc.netty.NettyPoolableFactory   : register success, cost <span class="hljs-number">125</span> ms, version:<span class="hljs-number">0.4</span>.1,role:RMROLE,channel:[id: <span class="hljs-number">0xe6468995</span>, L:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">57397</span> - R:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">8091</span>]

</code></pre>
<p>日志中可以看到</p>
<ol>
<li>创建Netty连接</li>
<li>发送注册请求</li>
<li>得到响应结果</li>
<li><code>RmRpcClient</code>、<code>TmRpcClient</code>成功实例化</li>
</ol>
<h2>TM处理流程</h2>
<p>在本例中，TM的角色是business-service,BusinessService的purchase方法标注了<code>@GlobalTransactional</code>注解</p>
<pre><code class="language-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BusinessService</span> </span>{

    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> StorageFeignClient storageFeignClient;
    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> OrderFeignClient orderFeignClient;

    <span class="hljs-meta">@GlobalTransactional</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">purchase</span><span class="hljs-params">(String userId, String commodityCode, <span class="hljs-keyword">int</span> orderCount)</span></span>{
        storageFeignClient.deduct(commodityCode, orderCount);

        orderFeignClient.create(userId, commodityCode, orderCount);
    }
}
</code></pre>
<p>方法调用后将会创建一个全局事务，首先关注<code>@GlobalTransactional</code>注解的作用，在<a href="https://github.com/seata/seata/blob/develop/spring/src/main/java/com/alibaba/fescar/spring/annotation/GlobalTransactionalInterceptor.java">GlobalTransactionalInterceptor</a>中被拦截处理</p>
<pre><code class="language-java"><span class="hljs-comment">/**
 * AOP拦截方法调用
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(<span class="hljs-keyword">final</span> MethodInvocation methodInvocation)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
    Class&lt;?&gt; targetClass = (methodInvocation.getThis() != <span class="hljs-keyword">null</span> ? AopUtils.getTargetClass(methodInvocation.getThis()) : <span class="hljs-keyword">null</span>);
    Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
    <span class="hljs-keyword">final</span> Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);

	<span class="hljs-comment">//获取方法GlobalTransactional注解</span>
    <span class="hljs-keyword">final</span> GlobalTransactional globalTransactionalAnnotation = getAnnotation(method, GlobalTransactional.class);
    <span class="hljs-keyword">final</span> GlobalLock globalLockAnnotation = getAnnotation(method, GlobalLock.class);
    
    <span class="hljs-comment">//如果方法有GlobalTransactional注解，则拦截到相应方法处理</span>
    <span class="hljs-keyword">if</span> (globalTransactionalAnnotation != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">return</span> handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (globalLockAnnotation != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">return</span> handleGlobalLock(methodInvocation);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> methodInvocation.proceed();
    }
}
</code></pre>
<p><code>handleGlobalTransaction</code>方法中对<a href="https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java">TransactionalTemplate</a>的execute进行了调用，从类名可以看到这是一个标准的模版方法，它定义了TM对全局事务处理的标准步骤，注释已经比较清楚了</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">execute</span><span class="hljs-params">(TransactionalExecutor business)</span> <span class="hljs-keyword">throws</span> TransactionalExecutor.ExecutionException </span>{
    <span class="hljs-comment">// 1. get or create a transaction</span>
    GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();

    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 2. begin transaction</span>
        <span class="hljs-keyword">try</span> {
            triggerBeforeBegin();
            tx.begin(business.timeout(), business.name());
            triggerAfterBegin();
        } <span class="hljs-keyword">catch</span> (TransactionException txe) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.BeginFailure);
        }
        Object rs = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// Do Your Business</span>
            rs = business.execute();
        } <span class="hljs-keyword">catch</span> (Throwable ex) {
            <span class="hljs-comment">// 3. any business exception, rollback.</span>
            <span class="hljs-keyword">try</span> {
                triggerBeforeRollback();
                tx.rollback();
                triggerAfterRollback();
                <span class="hljs-comment">// 3.1 Successfully rolled back</span>
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
            } <span class="hljs-keyword">catch</span> (TransactionException txe) {
                <span class="hljs-comment">// 3.2 Failed to rollback</span>
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.RollbackFailure, ex);
            }
        }
        <span class="hljs-comment">// 4. everything is fine, commit.</span>
        <span class="hljs-keyword">try</span> {
            triggerBeforeCommit();
            tx.commit();
            triggerAfterCommit();
        } <span class="hljs-keyword">catch</span> (TransactionException txe) {
            <span class="hljs-comment">// 4.1 Failed to commit</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.CommitFailure);
        }
        <span class="hljs-keyword">return</span> rs;
    } <span class="hljs-keyword">finally</span> {
        <span class="hljs-comment">//5. clear</span>
        triggerAfterCompletion();
        cleanUp();
    }
}
</code></pre>
<p>通过<a href="https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/api/DefaultGlobalTransaction.java">DefaultGlobalTransaction</a>的begin方法开启全局事务</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">begin</span><span class="hljs-params">(<span class="hljs-keyword">int</span> timeout, String name)</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
    <span class="hljs-keyword">if</span> (role != GlobalTransactionRole.Launcher) {
        check();
        <span class="hljs-keyword">if</span> (LOGGER.isDebugEnabled()) {
            LOGGER.debug(<span class="hljs-string">"Ignore Begin(): just involved in global transaction ["</span> + xid + <span class="hljs-string">"]"</span>);
        }
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">if</span> (xid != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException();
    }
    <span class="hljs-keyword">if</span> (RootContext.getXID() != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException();
    }
    <span class="hljs-comment">//具体开启事务的方法，获取TC返回的XID</span>
    xid = transactionManager.begin(<span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>, name, timeout);
    status = GlobalStatus.Begin;
    RootContext.bind(xid);
    <span class="hljs-keyword">if</span> (LOGGER.isDebugEnabled()) {
        LOGGER.debug(<span class="hljs-string">"Begin a NEW global transaction ["</span> + xid + <span class="hljs-string">"]"</span>);
    }
}
</code></pre>
<p>方法开头处<code>if (role != GlobalTransactionRole.Launcher)</code>对role的判断有关键的作用，表明当前是全局事务的发起者（Launcher）还是参与者（Participant）。如果在分布式事务的下游系统方法中也加上<code>@GlobalTransactional</code>注解，那么它的角色就是Participant，会忽略后面的begin直接return，而判断是Launcher还是Participant是根据当前上下文是否已存在XID来判断，没有XID的就是Launcher，已经存在XID的就是Participant.
由此可见，全局事务的创建只能由Launcher执行，而一次分布式事务中也只有一个Launcher存在。</p>
<p><a href="https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/DefaultTransactionManager.java">DefaultTransactionManager</a>负责TM与TC通讯，发送begin、commit、rollback指令</p>
<pre><code class="language-java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">begin</span><span class="hljs-params">(String applicationId, String transactionServiceGroup, String name, <span class="hljs-keyword">int</span> timeout)</span>
    <span class="hljs-keyword">throws</span> TransactionException </span>{
    GlobalBeginRequest request = <span class="hljs-keyword">new</span> GlobalBeginRequest();
    request.setTransactionName(name);
    request.setTimeout(timeout);
    GlobalBeginResponse response = (GlobalBeginResponse)syncCall(request);
    <span class="hljs-keyword">return</span> response.getXid();
}
</code></pre>
<p>至此拿到fescar-server返回的XID表示一个全局事务创建成功，日志中也反应了上述流程</p>
<pre><code class="language-java"><span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">46</span>:<span class="hljs-number">57.417</span> DEBUG <span class="hljs-number">31326</span> --- [nio-<span class="hljs-number">8084</span>-exec-<span class="hljs-number">1</span>] c.a.f.c.rpc.netty.AbstractRpcRemoting    : offer message: timeout=<span class="hljs-number">60000</span>,transactionName=purchase(java.lang.String,java.lang.String,<span class="hljs-keyword">int</span>)
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">46</span>:<span class="hljs-number">57.417</span> DEBUG <span class="hljs-number">31326</span> --- [geSend_TMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : write message:FescarMergeMessage timeout=<span class="hljs-number">60000</span>,transactionName=purchase(java.lang.String,java.lang.String,<span class="hljs-keyword">int</span>), channel:[id: <span class="hljs-number">0xa148545e</span>, L:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">56120</span> - R:/<span class="hljs-number">127.0</span>.0.1:<span class="hljs-number">8091</span>],active?<span class="hljs-keyword">true</span>,writable?<span class="hljs-keyword">true</span>,isopen?<span class="hljs-keyword">true</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">46</span>:<span class="hljs-number">57.418</span> DEBUG <span class="hljs-number">31326</span> --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:FescarMergeMessage timeout=<span class="hljs-number">60000</span>,transactionName=purchase(java.lang.String,java.lang.String,<span class="hljs-keyword">int</span>)
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">46</span>:<span class="hljs-number">57.421</span> DEBUG <span class="hljs-number">31326</span> --- [lector_TMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:MergeResultMessage com.alibaba.fescar.core.protocol.transaction.GlobalBeginResponse@<span class="hljs-number">2</span>dc480dc,messageId:<span class="hljs-number">1196</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">46</span>:<span class="hljs-number">57.421</span> DEBUG <span class="hljs-number">31326</span> --- [nio-<span class="hljs-number">8084</span>-exec-<span class="hljs-number">1</span>] c.a.fescar.core.context.RootContext      : bind <span class="hljs-number">192.168</span>.224.93:<span class="hljs-number">8091</span>:<span class="hljs-number">2008502699</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">13</span>:<span class="hljs-number">46</span>:<span class="hljs-number">57.421</span> DEBUG <span class="hljs-number">31326</span> --- [nio-<span class="hljs-number">8084</span>-exec-<span class="hljs-number">1</span>] c.a.f.tm.api.DefaultGlobalTransaction    : Begin a NEW global transaction [<span class="hljs-number">192.168</span>.224.93:<span class="hljs-number">8091</span>:<span class="hljs-number">2008502699</span>]
</code></pre>
<p>全局事务创建后，就开始执行business.execute()，即业务代码<code>storageFeignClient.deduct(commodityCode, orderCount)</code>进入RM处理流程，此处的业务逻辑为调用storage-service的扣减库存接口。</p>
<h2>RM处理流程</h2>
<pre><code class="language-java"><span class="hljs-meta">@GetMapping</span>(path = <span class="hljs-string">"/deduct"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> Boolean <span class="hljs-title">deduct</span><span class="hljs-params">(String commodityCode, Integer count)</span></span>{
    storageService.deduct(commodityCode,count);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}

<span class="hljs-meta">@Transactional</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">deduct</span><span class="hljs-params">(String commodityCode, <span class="hljs-keyword">int</span> count)</span></span>{
    Storage storage = storageDAO.findByCommodityCode(commodityCode);
    storage.setCount(storage.getCount()-count);

    storageDAO.save(storage);
}
</code></pre>
<p>storage的接口和service方法并未出现fescar相关的代码和注解，体现了fescar的无侵入。那它是如何加入到这次全局事务中的呢？答案在<a href="https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionProxy.java">ConnectionProxy</a>中，这也是前面说为什么必须要使用<code>DataSourceProxy</code>的原因，通过DataSourceProxy才能在业务代码的本地事务提交时，fescar通过该切入点，向TC注册分支事务并发送RM的处理结果。</p>
<p>由于业务代码本身的事务提交被<code>ConnectionProxy</code>代理实现，所以在提交本地事务时，实际执行的是ConnectionProxy的commit方法</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">commit</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException </span>{
	<span class="hljs-comment">//如果当前是全局事务，则执行全局事务的提交</span>
	<span class="hljs-comment">//判断是不是全局事务，就是看当前上下文是否存在XID</span>
    <span class="hljs-keyword">if</span> (context.inGlobalTransaction()) {
        processGlobalTransactionCommit();
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (context.isGlobalLockRequire()) {
        processLocalCommitWithGlobalLocks();
    } <span class="hljs-keyword">else</span> {
        targetConnection.commit();
    }
}
    
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processGlobalTransactionCommit</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException </span>{
    <span class="hljs-keyword">try</span> {
    	<span class="hljs-comment">//首先是向TC注册RM，拿到TC分配的branchId</span>
        register();
    } <span class="hljs-keyword">catch</span> (TransactionException e) {
        recognizeLockKeyConflictException(e);
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">if</span> (context.hasUndoLog()) {
        	<span class="hljs-comment">//写入undolog</span>
            UndoLogManager.flushUndoLogs(<span class="hljs-keyword">this</span>);
        }

		<span class="hljs-comment">//提交本地事务，写入undo_log和业务数据在同一个本地事务中</span>
        targetConnection.commit();
    } <span class="hljs-keyword">catch</span> (Throwable ex) {
    	<span class="hljs-comment">//向TC发送RM的事务处理失败的通知</span>
        report(<span class="hljs-keyword">false</span>);
        <span class="hljs-keyword">if</span> (ex <span class="hljs-keyword">instanceof</span> SQLException) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> SQLException(ex);
        }
    }
	<span class="hljs-comment">//向TC发送RM的事务处理成功的通知</span>
    report(<span class="hljs-keyword">true</span>);
    context.reset();
}
    
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
	<span class="hljs-comment">//注册RM，构建request通过netty向TC发送注册指令</span>
    Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(),
            <span class="hljs-keyword">null</span>, context.getXid(), <span class="hljs-keyword">null</span>, context.buildLockKeys());
    <span class="hljs-comment">//将返回的branchId存在上下文中</span>
    context.setBranchId(branchId);
}
</code></pre>
<p>通过日志印证一下上面的流程</p>
<pre><code class="language-java"><span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.341</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] o.s.c.a.f.web.FescarHandlerInterceptor   : xid in RootContext <span class="hljs-keyword">null</span> xid in RpcContext <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.341</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.fescar.core.context.RootContext      : bind <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.341</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] o.s.c.a.f.web.FescarHandlerInterceptor   : bind <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span> to RootContext
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.386</span>  INFO <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select storage0_.id as id1_0_, storage0_.commodity_code as commodit2_0_, storage0_.count as count3_0_ from storage_tbl storage0_ where storage0_.commodity_code=?
Hibernate: update storage_tbl set count=? where id=?
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.673</span>  INFO <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.fescar.core.rpc.netty.RmRpcClient    : will connect to <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.673</span>  INFO <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.fescar.core.rpc.netty.RmRpcClient    : RM will register :jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.673</span>  INFO <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.f.c.rpc.netty.NettyPoolableFactory   : NettyPool create channel to {<span class="hljs-string">"address"</span>:<span class="hljs-string">"192.168.0.2:8091"</span>,<span class="hljs-string">"message"</span>:{<span class="hljs-string">"applicationId"</span>:<span class="hljs-string">"storage-service"</span>,<span class="hljs-string">"byteBuffer"</span>:{<span class="hljs-string">"char"</span>:<span class="hljs-string">"\u0000"</span>,<span class="hljs-string">"direct"</span>:<span class="hljs-keyword">false</span>,<span class="hljs-string">"double"</span>:<span class="hljs-number">0.0</span>,<span class="hljs-string">"float"</span>:<span class="hljs-number">0.0</span>,<span class="hljs-string">"int"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"long"</span>:<span class="hljs-number">0</span>,<span class="hljs-string">"readOnly"</span>:<span class="hljs-keyword">false</span>,<span class="hljs-string">"short"</span>:<span class="hljs-number">0</span>},<span class="hljs-string">"resourceIds"</span>:<span class="hljs-string">"jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false"</span>,<span class="hljs-string">"transactionServiceGroup"</span>:<span class="hljs-string">"hello-service-fescar-service-group"</span>,<span class="hljs-string">"typeCode"</span>:<span class="hljs-number">103</span>,<span class="hljs-string">"version"</span>:<span class="hljs-string">"0.4.0"</span>},<span class="hljs-string">"transactionRole"</span>:<span class="hljs-string">"RMROLE"</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.677</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:RegisterRMRequest{resourceIds=<span class="hljs-string">'jdbc:mysql://127.0.0.1:3306/db_storage?useSSL=false'</span>, applicationId=<span class="hljs-string">'storage-service'</span>, transactionServiceGroup=<span class="hljs-string">'hello-service-fescar-service-group'</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.680</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:version=<span class="hljs-number">0.4</span>.1,extraData=<span class="hljs-keyword">null</span>,identified=<span class="hljs-keyword">true</span>,resultCode=<span class="hljs-keyword">null</span>,msg=<span class="hljs-keyword">null</span>,messageId:<span class="hljs-number">9</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.680</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : com.alibaba.fescar.core.rpc.netty.RmRpcClient@<span class="hljs-number">7</span>d61f5d4 msgId:<span class="hljs-number">9</span>, future :com.alibaba.fescar.core.protocol.MessageFuture@<span class="hljs-number">186</span>cd3e0, body:version=<span class="hljs-number">0.4</span>.1,extraData=<span class="hljs-keyword">null</span>,identified=<span class="hljs-keyword">true</span>,resultCode=<span class="hljs-keyword">null</span>,msg=<span class="hljs-keyword">null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.680</span>  INFO <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.fescar.core.rpc.netty.RmRpcClient    : register RM success. server version:<span class="hljs-number">0.4</span>.1,channel:[id: <span class="hljs-number">0xd40718e3</span>, L:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">62607</span> - R:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>]
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.680</span>  INFO <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.f.c.rpc.netty.NettyPoolableFactory   : register success, cost <span class="hljs-number">3</span> ms, version:<span class="hljs-number">0.4</span>.1,role:RMROLE,channel:[id: <span class="hljs-number">0xd40718e3</span>, L:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">62607</span> - R:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>]
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.680</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.f.c.rpc.netty.AbstractRpcRemoting    : offer message: transactionId=<span class="hljs-number">2008546211</span>,branchType=AT,resourceId=jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false,lockKey=storage_tbl:1</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.681</span> DEBUG <span class="hljs-number">38933</span> --- [geSend_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : write message:FescarMergeMessage transactionId=<span class="hljs-number">2008546211</span>,branchType=AT,resourceId=jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false,lockKey=storage_tbl:1, channel:[id: 0xd40718e3, L:/192.168.0.2:62607 - R:/192.168.0.2:8091],active?true,writable?true,isopen?true</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.681</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:FescarMergeMessage transactionId=<span class="hljs-number">2008546211</span>,branchType=AT,resourceId=jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false,lockKey=storage_tbl:1</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.687</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:MergeResultMessage BranchRegisterResponse: transactionId=<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,result code =Success,getMsg =<span class="hljs-keyword">null</span>,messageId:<span class="hljs-number">11</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.702</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.f.rm.datasource.undo.UndoLogManager  : Flushing UNDO LOG: {<span class="hljs-string">"branchId"</span>:<span class="hljs-number">2008546212</span>,<span class="hljs-string">"sqlUndoLogs"</span>:[{<span class="hljs-string">"afterImage"</span>:{<span class="hljs-string">"rows"</span>:[{<span class="hljs-string">"fields"</span>:[{<span class="hljs-string">"keyType"</span>:<span class="hljs-string">"PrimaryKey"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"id"</span>,<span class="hljs-string">"type"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"value"</span>:<span class="hljs-number">1</span>},{<span class="hljs-string">"keyType"</span>:<span class="hljs-string">"NULL"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"count"</span>,<span class="hljs-string">"type"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"value"</span>:<span class="hljs-number">993</span>}]}],<span class="hljs-string">"tableName"</span>:<span class="hljs-string">"storage_tbl"</span>},<span class="hljs-string">"beforeImage"</span>:{<span class="hljs-string">"rows"</span>:[{<span class="hljs-string">"fields"</span>:[{<span class="hljs-string">"keyType"</span>:<span class="hljs-string">"PrimaryKey"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"id"</span>,<span class="hljs-string">"type"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"value"</span>:<span class="hljs-number">1</span>},{<span class="hljs-string">"keyType"</span>:<span class="hljs-string">"NULL"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"count"</span>,<span class="hljs-string">"type"</span>:<span class="hljs-number">4</span>,<span class="hljs-string">"value"</span>:<span class="hljs-number">994</span>}]}],<span class="hljs-string">"tableName"</span>:<span class="hljs-string">"storage_tbl"</span>},<span class="hljs-string">"sqlType"</span>:<span class="hljs-string">"UPDATE"</span>,<span class="hljs-string">"tableName"</span>:<span class="hljs-string">"storage_tbl"</span>}],<span class="hljs-string">"xid"</span>:<span class="hljs-string">"192.168.0.2:8091:2008546211"</span>}
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.755</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.f.c.rpc.netty.AbstractRpcRemoting    : offer message: transactionId=<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,resourceId=<span class="hljs-keyword">null</span>,status=PhaseOne_Done,applicationData=<span class="hljs-keyword">null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.755</span> DEBUG <span class="hljs-number">38933</span> --- [geSend_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : write message:FescarMergeMessage transactionId=<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,resourceId=<span class="hljs-keyword">null</span>,status=PhaseOne_Done,applicationData=<span class="hljs-keyword">null</span>, channel:[id: <span class="hljs-number">0xd40718e3</span>, L:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">62607</span> - R:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>],active?<span class="hljs-keyword">true</span>,writable?<span class="hljs-keyword">true</span>,isopen?<span class="hljs-keyword">true</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.756</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:FescarMergeMessage transactionId=<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,resourceId=<span class="hljs-keyword">null</span>,status=PhaseOne_Done,applicationData=<span class="hljs-keyword">null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.758</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:MergeResultMessage com.alibaba.fescar.core.protocol.transaction.BranchReportResponse@<span class="hljs-number">582</span>a08cf,messageId:<span class="hljs-number">13</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.799</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] c.a.fescar.core.context.RootContext      : unbind <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">48.799</span> DEBUG <span class="hljs-number">38933</span> --- [nio-<span class="hljs-number">8081</span>-exec-<span class="hljs-number">1</span>] o.s.c.a.f.web.FescarHandlerInterceptor   : unbind <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span> from RootContext
</code></pre>
<ol>
<li>获取business-service传来的XID</li>
<li>绑定XID到当前上下文中</li>
<li>执行业务逻辑sql</li>
<li>向TC创建本次RM的Netty连接</li>
<li>向TC发送分支事务的相关信息</li>
<li>获得TC返回的branchId</li>
<li>记录Undo Log数据</li>
<li>向TC发送本次事务PhaseOne阶段的处理结果</li>
<li>从当前上下文中解绑XID</li>
</ol>
<p>其中第1步和第9步，是在<a href="https://github.com/dongsheep/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-fescar/src/main/java/org/springframework/cloud/alibaba/fescar/web/FescarHandlerInterceptor.java">FescarHandlerInterceptor</a>中完成的，该类并不属于fescar，是前面提到的spring-cloud-alibaba-fescar,它实现了基于feign、rest通信时将xid bind和unbind到当前请求上下文中。到这里RM完成了PhaseOne阶段的工作，接着看PhaseTwo阶段的处理逻辑。</p>
<h2>事务提交</h2>
<p>各分支事务执行完成后，TC对各RM的汇报结果进行汇总，给各RM发送commit或rollback的指令</p>
<pre><code class="language-java"><span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.813</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Receive:xid=<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,branchType=AT,resourceId=jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false,applicationData=null,messageId:1</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.813</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.AbstractRpcRemoting    : com.alibaba.fescar.core.rpc.netty.RmRpcClient@<span class="hljs-number">7</span>d61f5d4 msgId:<span class="hljs-number">1</span>, body:xid=<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,branchType=AT,resourceId=jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false,applicationData=null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.814</span>  INFO <span class="hljs-number">38933</span> --- [atch_RMROLE_1_8] c.a.f.core.rpc.netty.RmMessageListener   : onMessage:xid=<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span>,branchId=<span class="hljs-number">2008546212</span>,branchType=AT,resourceId=jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false,applicationData=null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.816</span>  INFO <span class="hljs-number">38933</span> --- [atch_RMROLE_1_8] com.alibaba.fescar.rm.AbstractRMHandler  : Branch committing: <span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>:<span class="hljs-number">2008546211</span> <span class="hljs-number">2008546212</span> jdbc:mysql:<span class="hljs-comment">//127.0.0.1:3306/db_storage?useSSL=false null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.816</span>  INFO <span class="hljs-number">38933</span> --- [atch_RMROLE_1_8] com.alibaba.fescar.rm.AbstractRMHandler  : Branch commit result: PhaseTwo_Committed
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.817</span>  INFO <span class="hljs-number">38933</span> --- [atch_RMROLE_1_8] c.a.fescar.core.rpc.netty.RmRpcClient    : RmRpcClient sendResponse branchStatus=PhaseTwo_Committed,result code =Success,getMsg =<span class="hljs-keyword">null</span>
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.817</span> DEBUG <span class="hljs-number">38933</span> --- [atch_RMROLE_1_8] c.a.f.c.rpc.netty.AbstractRpcRemoting    : send response:branchStatus=PhaseTwo_Committed,result code =Success,getMsg =<span class="hljs-keyword">null</span>,channel:[id: <span class="hljs-number">0xd40718e3</span>, L:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">62607</span> - R:/<span class="hljs-number">192.168</span>.0.2:<span class="hljs-number">8091</span>]
<span class="hljs-number">2019</span>-<span class="hljs-number">04</span>-<span class="hljs-number">09</span> <span class="hljs-number">21</span>:<span class="hljs-number">57</span>:<span class="hljs-number">49.817</span> DEBUG <span class="hljs-number">38933</span> --- [lector_RMROLE_1] c.a.f.c.rpc.netty.MessageCodecHandler    : Send:branchStatus=PhaseTwo_Committed,result code =Success,getMsg =<span class="hljs-keyword">null</span>
</code></pre>
<p>从日志中可以看到</p>
<ol>
<li>RM收到XID=192.168.0.2:8091:2008546211,branchId=2008546212的commit通知</li>
<li>执行commit动作</li>
<li>将commit结果发送给TC，branchStatus为PhaseTwo_Committed</li>
</ol>
<p>具体看下二阶段commit的执行过程，在<a href="https://github.com/seata/seata/blob/develop/rm/src/main/java/com/alibaba/fescar/rm/AbstractRMHandler.java">AbstractRMHandler</a>类的doBranchCommit方法</p>
<pre><code class="language-java"><span class="hljs-comment">/**
 * 拿到通知的xid、branchId等关键参数
 * 然后调用RM的branchCommit
 */</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBranchCommit</span><span class="hljs-params">(BranchCommitRequest request, BranchCommitResponse response)</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
    String xid = request.getXid();
    <span class="hljs-keyword">long</span> branchId = request.getBranchId();
    String resourceId = request.getResourceId();
    String applicationData = request.getApplicationData();
    LOGGER.info(<span class="hljs-string">"Branch committing: "</span> + xid + <span class="hljs-string">" "</span> + branchId + <span class="hljs-string">" "</span> + resourceId + <span class="hljs-string">" "</span> + applicationData);
    BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId, applicationData);
    response.setBranchStatus(status);
    LOGGER.info(<span class="hljs-string">"Branch commit result: "</span> + status);
}
</code></pre>
<p>最终会将branchCommit的请求调用到<a href="https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/AsyncWorker.java">AsyncWorker</a>的branchCommit方法。AsyncWorker的处理方式是fescar架构的一个关键部分，因为大部分事务都是会正常提交的，所以在PhaseOne阶段就已经结束了，这样就可以将锁最快的释放。PhaseTwo阶段接收commit的指令后，异步处理即可。将PhaseTwo的时间消耗排除在一次分布式事务之外。</p>
<pre><code class="language-java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> List&lt;Phase2Context&gt; ASYNC_COMMIT_BUFFER = Collections.synchronizedList( <span class="hljs-keyword">new</span> ArrayList&lt;Phase2Context&gt;());
        
<span class="hljs-comment">/**
 * 将需要提交的XID加入list
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> BranchStatus <span class="hljs-title">branchCommit</span><span class="hljs-params">(BranchType branchType, String xid, <span class="hljs-keyword">long</span> branchId, String resourceId, String applicationData)</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
    <span class="hljs-keyword">if</span> (ASYNC_COMMIT_BUFFER.size() &lt; ASYNC_COMMIT_BUFFER_LIMIT) {
        ASYNC_COMMIT_BUFFER.add(<span class="hljs-keyword">new</span> Phase2Context(branchType, xid, branchId, resourceId, applicationData));
    } <span class="hljs-keyword">else</span> {
        LOGGER.warn(<span class="hljs-string">"Async commit buffer is FULL. Rejected branch ["</span> + branchId + <span class="hljs-string">"/"</span> + xid + <span class="hljs-string">"] will be handled by housekeeping later."</span>);
    }
    <span class="hljs-keyword">return</span> BranchStatus.PhaseTwo_Committed;
}
	
<span class="hljs-comment">/**
 * 通过定时任务消费list中的XID
 */</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{
    LOGGER.info(<span class="hljs-string">"Async Commit Buffer Limit: "</span> + ASYNC_COMMIT_BUFFER_LIMIT);
    timerExecutor = <span class="hljs-keyword">new</span> ScheduledThreadPoolExecutor(<span class="hljs-number">1</span>,
        <span class="hljs-keyword">new</span> NamedThreadFactory(<span class="hljs-string">"AsyncWorker"</span>, <span class="hljs-number">1</span>, <span class="hljs-keyword">true</span>));
    timerExecutor.scheduleAtFixedRate(<span class="hljs-keyword">new</span> Runnable() {
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{
            <span class="hljs-keyword">try</span> {
                doBranchCommits();
            } <span class="hljs-keyword">catch</span> (Throwable e) {
                LOGGER.info(<span class="hljs-string">"Failed at async committing ... "</span> + e.getMessage());
            }
        }
    }, <span class="hljs-number">10</span>, <span class="hljs-number">1000</span> * <span class="hljs-number">1</span>, TimeUnit.MILLISECONDS);
}
	
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBranchCommits</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span> (ASYNC_COMMIT_BUFFER.size() == <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span>;
    }
    Map&lt;String, List&lt;Phase2Context&gt;&gt; mappedContexts = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
    Iterator&lt;Phase2Context&gt; iterator = ASYNC_COMMIT_BUFFER.iterator();
    
    <span class="hljs-comment">//一次定时循环取出ASYNC_COMMIT_BUFFER中的所有待办数据</span>
    <span class="hljs-comment">//以resourceId作为key分组待commit数据，resourceId是一个数据库的连接url</span>
    <span class="hljs-comment">//在前面的日志中可以看到，目的是为了覆盖应用的多数据源创建</span>
    <span class="hljs-keyword">while</span> (iterator.hasNext()) {
        Phase2Context commitContext = iterator.next();
        List&lt;Phase2Context&gt; contextsGroupedByResourceId = mappedContexts.get(commitContext.resourceId);
        <span class="hljs-keyword">if</span> (contextsGroupedByResourceId == <span class="hljs-keyword">null</span>) {
            contextsGroupedByResourceId = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();
            mappedContexts.put(commitContext.resourceId, contextsGroupedByResourceId);
        }
        contextsGroupedByResourceId.add(commitContext);

        iterator.remove();

    }

    <span class="hljs-keyword">for</span> (Map.Entry&lt;String, List&lt;Phase2Context&gt;&gt; entry : mappedContexts.entrySet()) {
        Connection conn = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">try</span> {
            	<span class="hljs-comment">//根据resourceId获取数据源以及连接</span>
                DataSourceProxy dataSourceProxy = DataSourceManager.get().get(entry.getKey());
                conn = dataSourceProxy.getPlainConnection();
            } <span class="hljs-keyword">catch</span> (SQLException sqle) {
                LOGGER.warn(<span class="hljs-string">"Failed to get connection for async committing on "</span> + entry.getKey(), sqle);
                <span class="hljs-keyword">continue</span>;
            }
            List&lt;Phase2Context&gt; contextsGroupedByResourceId = entry.getValue();
            <span class="hljs-keyword">for</span> (Phase2Context commitContext : contextsGroupedByResourceId) {
                <span class="hljs-keyword">try</span> {
                	<span class="hljs-comment">//执行undolog的处理，即删除xid、branchId对应的记录</span>
                    UndoLogManager.deleteUndoLog(commitContext.xid, commitContext.branchId, conn);
                } <span class="hljs-keyword">catch</span> (Exception ex) {
                    LOGGER.warn(
                        <span class="hljs-string">"Failed to delete undo log ["</span> + commitContext.branchId + <span class="hljs-string">"/"</span> + commitContext.xid + <span class="hljs-string">"]"</span>, ex);
                }
            }

        } <span class="hljs-keyword">finally</span> {
            <span class="hljs-keyword">if</span> (conn != <span class="hljs-keyword">null</span>) {
                <span class="hljs-keyword">try</span> {
                    conn.close();
                } <span class="hljs-keyword">catch</span> (SQLException closeEx) {
                    LOGGER.warn(<span class="hljs-string">"Failed to close JDBC resource while deleting undo_log "</span>, closeEx);
                }
            }
        }
    }
}
</code></pre>
<p>所以对于commit动作的处理，RM只需删除xid、branchId对应的undo_log即可。</p>
<h2>事务回滚</h2>
<p>对于rollback场景的触发有两种情况</p>
<ol>
<li>分支事务处理异常，即<a href="https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/ConnectionProxy.java">ConnectionProxy</a>中<code>report(false)</code>的情况</li>
<li>TM捕获到下游系统上抛的异常，即发起全局事务标有<code>@GlobalTransactional</code>注解的方法捕获到的异常。在前面<a href="https://github.com/seata/seata/blob/develop/tm/src/main/java/com/alibaba/fescar/tm/api/TransactionalTemplate.java">TransactionalTemplate</a>类的execute模版方法中，对business.execute()的调用进行了catch，catch后会调用rollback，由TM通知TC对应XID需要回滚事务</li>
</ol>
<pre><code class="language-java"> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">rollback</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
    <span class="hljs-comment">//只有Launcher能发起这个rollback</span>
    <span class="hljs-keyword">if</span> (role == GlobalTransactionRole.Participant) {
        <span class="hljs-comment">// Participant has no responsibility of committing</span>
        <span class="hljs-keyword">if</span> (LOGGER.isDebugEnabled()) {
            LOGGER.debug(<span class="hljs-string">"Ignore Rollback(): just involved in global transaction ["</span> + xid + <span class="hljs-string">"]"</span>);
        }
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">if</span> (xid == <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException();
    }

    status = transactionManager.rollback(xid);
    <span class="hljs-keyword">if</span> (RootContext.getXID() != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">if</span> (xid.equals(RootContext.getXID())) {
            RootContext.unbind();
        }
    }
}
</code></pre>
<p>TC汇总后向参与者发送rollback指令，RM在<a href="https://github.com/seata/seata/blob/develop/rm/src/main/java/com/alibaba/fescar/rm/AbstractRMHandler.java">AbstractRMHandler</a>类的doBranchRollback方法中接收这个rollback的通知</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBranchRollback</span><span class="hljs-params">(BranchRollbackRequest request, BranchRollbackResponse response)</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
    String xid = request.getXid();
    <span class="hljs-keyword">long</span> branchId = request.getBranchId();
    String resourceId = request.getResourceId();
    String applicationData = request.getApplicationData();
    LOGGER.info(<span class="hljs-string">"Branch rolling back: "</span> + xid + <span class="hljs-string">" "</span> + branchId + <span class="hljs-string">" "</span> + resourceId);
    BranchStatus status = getResourceManager().branchRollback(request.getBranchType(), xid, branchId, resourceId, applicationData);
    response.setBranchStatus(status);
    LOGGER.info(<span class="hljs-string">"Branch rollback result: "</span> + status);
}
</code></pre>
<p>然后将rollback请求传递到<code>DataSourceManager</code>类的branchRollback方法</p>
<pre><code class="language-java"><span class="hljs-function"><span class="hljs-keyword">public</span> BranchStatus <span class="hljs-title">branchRollback</span><span class="hljs-params">(BranchType branchType, String xid, <span class="hljs-keyword">long</span> branchId, String resourceId, String applicationData)</span> <span class="hljs-keyword">throws</span> TransactionException </span>{
    <span class="hljs-comment">//根据resourceId获取对应的数据源</span>
    DataSourceProxy dataSourceProxy = get(resourceId);
    <span class="hljs-keyword">if</span> (dataSourceProxy == <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ShouldNeverHappenException();
    }
    <span class="hljs-keyword">try</span> {
        UndoLogManager.undo(dataSourceProxy, xid, branchId);
    } <span class="hljs-keyword">catch</span> (TransactionException te) {
        <span class="hljs-keyword">if</span> (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) {
            <span class="hljs-keyword">return</span> BranchStatus.PhaseTwo_RollbackFailed_Unretryable;
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> BranchStatus.PhaseTwo_RollbackFailed_Retryable;
        }
    }
    <span class="hljs-keyword">return</span> BranchStatus.PhaseTwo_Rollbacked;
}
</code></pre>
<p>最终会执行<a href="https://github.com/seata/seata/blob/develop/rm-datasource/src/main/java/com/alibaba/fescar/rm/datasource/undo/UndoLogManager.java">UndoLogManager</a>类的undo方法，因为是纯jdbc操作代码比较长就不贴出来了，可以通过连接到github查看源码，说一下undo的具体流程</p>
<ol>
<li>根据xid和branchId查找PhaseOne阶段提交的undo_log</li>
<li>如果找到了就根据undo_log中记录的数据生成回放sql并执行，即还原PhaseOne阶段修改的数据</li>
<li>第2步处理完后，删除该条undo_log数据</li>
<li>如果第1步没有找到对应的undo_log，就插入一条状态为<code>GlobalFinished</code>的undo_log.
出现没找到的原因可能是PhaseOne阶段的本地事务异常了，导致没有正常写入。
因为xid和branchId是唯一索引，所以第4步的插入，可以防止PhaseOne阶段恢复后的成功写入，那么PhaseOne阶段就会异常，这样一来业务数据也就不会提交成功，数据达到了最终回滚了的效果</li>
</ol>
<h2>总结</h2>
<p>本地结合分布式业务场景，分析了fescar client侧的主要处理流程，对TM和RM角色的主要源码进行了解析，希望能对大家理解fescar的工作原理有所帮助。</p>
<p>随着fescar的快速迭代以及后期的Roadmap规划，假以时日相信fescar能够成为开源分布式事务的标杆解决方案。</p>
</section><footer class="footer-container"><div class="footer-body"><img src="/img/seata_logo_gray.png"/><p class="docsite-power">website powered by docsite</p><div class="cols-container"><div class="col col-12"><h3>愿景</h3><p>Seata 是一款阿里巴巴开源的分布式事务解决方案，致力于在微服务架构下提供高性能和简单易用的分布式事务服务。</p></div><div class="col col-6"><dl><dt>文档</dt><dd><a href="/zh-cn/docs/overview/what-is-seata.html" target="_self">Seata 是什么？</a></dd><dd><a href="/zh-cn/docs/user/quickstart.html" target="_self">快速开始</a></dd><dd><a href="https://github.com/seata/seata.github.io/issues/new" target="_self">报告文档问题</a></dd><dd><a href="https://github.com/seata/seata.github.io" target="_self">在Github上编辑此文档</a></dd></dl></div><div class="col col-6"><dl><dt>资源</dt><dd><a href="/zh-cn/blog/index.html" target="_self">博客</a></dd><dd><a href="/zh-cn/community/index.html" target="_self">社区</a></dd></dl></div></div><div class="copyright"><span>Copyright © 2019 Seata</span></div></div></footer></div></div>
	<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
	<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
	<script>
		window.rootPath = '';
  </script>
	<script src="/build/blogDetail.js"></script>
	<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?104e73ef0c18b416b27abb23757ed8ee";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
    </script>
</body>
</html>
